﻿using System;
using System.Linq;
using LinqExpressions = System.Linq.Expressions;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Reflection;
using System.Collections.Generic;

namespace Caliburn.Micro.BikeWizard.Utils
{
    /// <summary>
    /// Executes a caller specified action (i.e. preActionToPerform) and then starts listening for SingleEventWaiter Completed events. 
    /// When all SingleEventWaiter objects have fired a Completed event, a caller specified action (i.e. postActionToPerform) is executed 
    /// and finally the Completed event is fired.
    /// 
    /// Note:
    /// 
    /// When one or more SingleEventWaiter objects fail to fire a Completed event, the MultipleEventWaiter would wait forever.
    /// Therefore, after a certain time a SingleEventWaiter fires an event that signals failure.
    /// </summary>
    public class MultipleEventWaiter : IResult
    {
        #region Private Members
        private int completedEventWaiterCount = 0;
        private int toBeCompletedEventWaiterCount;
        private System.Action preActionToPerform;
        private System.Action<Exception> postActionToPerform;
        #endregion

        #region Constructors
        /// <summary>
        /// Execute actionToPerform and then wait for the list of MultipleEventWaiter objects to fire the Completed event
        /// </summary>
        /// <param name="preActionToPerform">The action to perform (this action should trigger firing the events that the SingleEventWaiter objects listen for)</param>
        /// <param name="eventWaiters">The list of SingleEventWaiter objects (each will listen for the specified number of events of a specific event type)</param>
        public MultipleEventWaiter(
           System.Action preActionToPerform,
           params SingleEventWaiter[] eventWaiters)
            : this(preActionToPerform, (error) => { }, eventWaiters)
        {
        }

        /// <summary>
        /// Execute actionToPerform and then wait for the list of MultipleEventWaiter objects to fire the Completed event
        /// </summary>
        /// <param name="preActionToPerform">The action to perform (this action should trigger firing the events that the SingleEventWaiter objects listen for)</param>
        /// <param name="postActionToPerform">The action to perform when all SingleEventWaiter objects have fired the Completed event</param>
        /// <param name="eventWaiters">The list of SingleEventWaiter objects (each will listen for the specified number of events of a specific event type)</param>
        public MultipleEventWaiter(
            System.Action preActionToPerform,
            System.Action<Exception> postActionToPerform,
            params SingleEventWaiter[] eventWaiters)
        {
            this.preActionToPerform = preActionToPerform;
            this.postActionToPerform = postActionToPerform;

            toBeCompletedEventWaiterCount = eventWaiters.Count();

            foreach (SingleEventWaiter eventWaiter in eventWaiters)
            {
                eventWaiter.Completed += eventWaiter_Completed;
                eventWaiter.TimedOut += eventWaiter_TimedOut;
            }
        }
        #endregion

        #region Private Methods
        private void eventWaiter_TimedOut(object sender, EventWaiterEventArgs e)
        {
            // Create exception object
            var ex = new Exception(
                String.Format("Event: {0} for object: {1} did not fire {2} times in {3} seconds",
                e.EventName, e.EventPublisher, e.RequiredEventCount, e.TimeWaited.TotalSeconds));

            // Allow postActionToPerform to execute
            this.postActionToPerform(ex);

            // And then throw the exception
            throw ex;
        }

        private void eventWaiter_Completed(object sender, EventArgs e)
        {
            completedEventWaiterCount++;

            if (completedEventWaiterCount >= toBeCompletedEventWaiterCount)
            {
                this.postActionToPerform(null);
                this.Completed(this, new ResultCompletionEventArgs() { Error = null, WasCancelled = false });
            }
        }
        #endregion

        #region IResult Implementation
        public void Execute(ActionExecutionContext context)
        {
            preActionToPerform();
        }

        public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };
        #endregion
    }
}
